package com.cskaoyan.javase.object._3equals;

/**
 * 方法的声明
 * equals方法的声明，比较简单：
 *      public boolean equals(Object obj)
 * 仅需要注意两点：
 *      1,该方法是有参数的，需要传入一个对象（任意一个对象就行）
 *      2.方法是有返回值的，返回一个布尔类型的值，真或假。
 *
 * 方法的作用:
 *      equals是相同,相等的意思
 *      所以这个方法的作用就是用来判断两个对象是否相等,相同.
 *      哪两个对象呢?
 *      1.方法调用者的对象
 *      2.方法传入的对象
 *
 * 既然是比较对象相等，那自然就需要比较的规则才有意义
 * 比较的规则其实就是方法体中的代码，在方法体当中如何写这个方法的实现
 * 就是写判断两个对象是否相等的比较规则
 * 我们先来看一下Object类当中，默认的比较规则，即equals方法的默认实现。
 *
 * equals方法的默认实现,默认比较对象相等的规则:
 *      return (this == obj);
 * Object类当中,直接用"=="来判断两个引用指向的对象是否相等
 * 也就是说,只有两个引用完全指向同一个对象时,对象才相等,说白了自己和自己相等.
 * 这个条件显然是过于苛刻了,在开发中很多情况下,不需要这么苛刻的条件
 * 所以子类应该重写父类中的方法,重写它的实现
 *
 * 我们认为的对象相等的规则是:
 *      1.如果不是相同类型的对象,没有可比性,直接认定是不相等的
 *      2.如果是相同类型的对象,那就具有可比性,这时只需要属性的取值一样,我们就认定两个对象是相等的
 *
 * equals方法重写的常规协定：
 *      1.自反性：对于任何非空引用值 x，x.equals(x)都应返回 true
 *      自己和自己比较,肯定是true
 *
 *      2.排他性：当比对的不是同种类型的对象或者是一个null时，默认返回false
 *      如何判断两个引用指向的对象是不是相同的数据类型?
 *      getClass方法比较两个引用所指向的Class对象是不是同一个对象,如果是同一个对象,说明是相同类型
 *      如果不是同一个对象,说明不是相同类型
 *
 *      3.对称性：对于任何非空引用值 x 和 y，当且仅当y.equals(x) 返回 true 时，x.equals(y) 才应返回true
 *      x=y -> y=x
 *      4.传递性：对于任何非空引用值 x、y 和 z，如果x.equals(y)返回 true，并且 y.equals(z) 返回 true，那么x.equals(z) 应返回 true。
 *      x = y , y = z -> x = z
 *      5.一致性：对于任何非空引用值 x 和 y，多次调用 x.equals(y)始终返回 true 或始终返回 false。当然前提是,用来做比较判断相等的条件没有发生变化.
 *      以上对称性,传递性,一致性,只需要按照成员变量的取值来判断对象相等
 *          那就能够直接满足了.
 *
 * 以上就是equals方法的基本使用
 * 细节问题/注意事项:
 *      1.排它性的实现有两种方式,getClass和instanceof,instanceof更宽松,允许传入子类对象
 *      2.可以用快捷键alt + insert来自动生成equals方法
 *      3.实际开发中,判断浮点数的大小相等,不要用"==",而是要用包装类型中compare(v1,v2)方法
 *          Float.compare(f1,f2)
 *          Double.compare(d1,d2)
 *      "=="在判断浮点数大小时,基本上不会出问题,日常使用一下是完全可以的,但是浮点数不仅仅是表示正常的数字,还可以表示一些特殊值,比如无穷大, 无穷小,NaN(非数)等,所以当浮点数的取值是这些特殊值时,"=="是无从判断的.
 *从严谨的角度考虑,判断浮点数大小,用"=="无法判断这些特殊值的,所以就需要这个compare方法
 * compare方法的原理是: 如果浮点数表示普通值,直接和"=="一样判断大小,当表示特殊值时,直接比较二进制位
 * 总之,浮点数的大小比较,建议使用该方法,不要用"=="
 *      compare(v1,v2)该方法的返回值有三种可能性:
 *      把这个方法调用理解为v1-v2
 *          1.负数,说明v2大,v1小
 *          2.正数,说明v1大,v2小
 *          3.0,说明相等
 *
 *
 * 4.如果类中有引用数据类型成员变量，需要去调用它们的equals方法完成比较。这就意味着还需要重写这个类的equals方法。
 * 5.财务金额上的运算是不推荐使用浮点数的，会出现精度问题。推荐使用BigDecimal这个类完成运算。
 *
 * @since 11:30
 * @author wuguidong@cskaoyan.onaliyun.com
 */
public class Demo {
    public static void main(String[] args) {
        Student s1 = new Student(18, 700);
        Student s2 = new Student(18, 700);
        System.out.println(s1 == s2);
        System.out.println(s1.equals(s2));
    }
}

class Student {
    int age;
    double score;

    public Student(int age, double score) {
        this.age = age;
        this.score = score;
    }

    // 按照属性的取值比较,手动重写equals方法
    /*@Override
    public boolean equals(Object obj) {
        // 1.满足自反性
        if (this == obj) {
            return true;
        }
        // 2.排它性,排除方法传入的类型不是同种类型以及null,下面的写法是标准格式,固定格式
        if (obj == null || getClass() != obj.getClass()) {
            return false;
        }
        // 对称性,传递性,一致性,只需要按照成员变量的取值来判断对象相等,那就能够直接满足了
        // 这里已经缺点obj对象的类型一定是Student类型了,所以可以直接强转,无需再判断了
        Student target = (Student) obj;
        // 比较属性的取值
        if (this.age != target.age){
            return false;
        }
        // 最后比较score的取值
        return this.score == target.score;
    }
*/

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        // 允许传入子类对象作为参数
        if (!(o instanceof Student)) return false;

        Student student = (Student) o;

        if (age != student.age) return false;
        return Double.compare(student.score, score) == 0;
    }


    @Override
    public int hashCode() {
        int result;
        long temp;
        result = age;
        temp = Double.doubleToLongBits(score);
        result = 31 * result + (int) (temp ^ (temp >>> 32));
        return result;
    }


// public void test(){
    //     System.out.println(age);
    // }
}